/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
#define LOG_DOMAIN "enumeration"
#include "cxxlog.h"
#include "vm_log.h"

#include "jit_import_rt.h"
#include "root_set_enum_internal.h"
#include "GlobalClassLoaderIterator.h"
#include "jit_intf_cpp.h"
#include "interpreter.h"
#include "vm_stats.h"
#include "m2n.h"
#include "open/vm_util.h"
#include "open/gc.h"
#include "open/vm_method_access.h"
#include "finalize.h"
#include "cci.h"
#include "vtable.h"

void vm_enumerate_interned_strings()
{
    TRACE2("enumeration", "vm_enumerate_interned_strings()");
    // string enumeration should be done in stop_the_world phase.
    // see String_Pool for more information
    String *ps = VM_Global_State::loader_env->string_pool.get_first_string_intern();
    // 20030405 Don't enumerate references that are *unmanaged null* (i.e. zero/NULL)
    // since vm_enumerate_root_reference() expects to be called with slots containing managed refs.
    REFS_RUNTIME_SWITCH_IF
#ifdef REFS_RUNTIME_OR_COMPRESSED
        while (ps != NULL) {
            COMPRESSED_REFERENCE compressed_ref = ps->intern.compressed_ref;
            assert(is_compressed_reference(compressed_ref));
            assert(compressed_ref != 0);
            vm_enumerate_compressed_root_reference((COMPRESSED_REFERENCE *)&ps->intern.compressed_ref, 
                VM_Global_State::loader_env->pin_interned_strings);
            ps = VM_Global_State::loader_env->string_pool.get_next_string_intern();
        }
#endif // REFS_RUNTIME_OR_COMPRESSED
    REFS_RUNTIME_SWITCH_ELSE
#ifdef REFS_RUNTIME_OR_UNCOMPRESSED
        while (ps != NULL) {
            ManagedObject* s = ps->intern.raw_ref;
            assert(s != NULL);
            vm_enumerate_root_reference((void **)&(ps->intern.raw_ref), 
                VM_Global_State::loader_env->pin_interned_strings);
            ps = VM_Global_State::loader_env->string_pool.get_next_string_intern();
        }
#endif // REFS_RUNTIME_OR_UNCOMPRESSED
    REFS_RUNTIME_SWITCH_ENDIF
} //vm_enumerate_interned_strings




// Enumerate all globally visible classes and their static fields.

static void vm_enumerate_jlc(Class* c, bool b_weak = false)
{
    assert (*c->get_class_handle());
    if (!b_weak) {
        vm_enumerate_root_reference((void**)c->get_class_handle(), FALSE);
    }
    else {
        vm_enumerate_weak_root_reference((void**)c->get_class_handle(), FALSE);
    }
}

static void vm_enumerate_class_static(Class* c)
{
    assert (c);
    ConstPoolEntry* cp = c->get_constant_pool().get_error_chain();
    while(cp) {
        vm_enumerate_root_reference((void**)(&(cp->error.cause)), FALSE);
        cp = cp->error.next;
    }
    // Finally enumerate the static fields of the class
    unsigned n_fields = c->get_number_of_fields();
    if(c->is_at_least_prepared()) {
        // Class has been prepared, so we can iterate over all its fields.
        for(unsigned i = 0; i < n_fields; i++) {
            Field* f = c->get_field(i);
            if(f->is_static()) {
                if(field_is_enumerable_reference(f)){
                    // The field is static and it is a reference.
                    REFS_RUNTIME_SWITCH_IF
#ifdef REFS_RUNTIME_OR_COMPRESSED
                        vm_enumerate_compressed_root_reference((U_32 *)f->get_address(), FALSE);
#endif // REFS_RUNTIME_OR_COMPRESSED
                    REFS_RUNTIME_SWITCH_ELSE
#ifdef REFS_RUNTIME_OR_UNCOMPRESSED
                        vm_enumerate_root_reference((void **)f->get_address(), FALSE);
#endif // REFS_RUNTIME_OR_UNCOMPRESSED
                    REFS_RUNTIME_SWITCH_ENDIF
                }
            }
        }
    }
}

void vm_enumerate_static_fields()
{
    TRACE2("enumeration", "vm_enumerate_static_fields()");
    assert(!hythread_is_suspend_enabled());
    GlobalClassLoaderIterator ClIterator;
    Boolean do_class_unloading = gc_supports_class_unloading();
    ClassLoader *cl = ClIterator.first();
    while(cl) {
        GlobalClassLoaderIterator::ClassIterator itc;
        GlobalClassLoaderIterator::ReportedClasses RepClasses = cl->GetReportedClasses();
        Class* c;
        for (itc = RepClasses->begin(); itc != RepClasses->end(); itc++)
        {
          c = itc->second;
          assert(c);
          vm_enumerate_jlc(c);
          vm_enumerate_class_static(c);
        }
        ClassTable::iterator itl;
        ClassTable* p_loadedClasses = cl->GetLoadedClasses();
        for (itl = p_loadedClasses->begin(); itl != p_loadedClasses->end(); itl++)
        {
            c = itl->second;
            assert(c);
            if (!cl->IsBootstrap())
            {
                vm_enumerate_jlc(c, do_class_unloading/*enum as weak root if gc supports that*/);
                vm_enumerate_class_static(c);
            }
        }
        cl = ClIterator.next();
   }
 } //vm_enumerate_static_fields


// 20030405 Note: When compressing references, vm_enumerate_root_reference() expects to be called with slots
// containing *managed* refs (represented by heap_base if null, not 0/NULL), so those refs must not be NULL. 
#ifdef _DEBUG
static void check_ref(void** ref)
{
#ifdef REFS_RUNTIME_OR_COMPRESSED
    REFS_RUNTIME_SWITCH_IF
        // 20030324 DEBUG: verify the slot whose reference is being passed.
        ManagedObject **p_obj = (ManagedObject **)ref;  
        ManagedObject* obj = *p_obj;
        assert(obj != NULL);    // See the comment at the top of the procedure.
        if ((void *)obj != VM_Global_State::loader_env->heap_base) {
            assert(((POINTER_SIZE_INT)VM_Global_State::loader_env->heap_base <= (POINTER_SIZE_INT)obj)
                && ((POINTER_SIZE_INT)obj <= (POINTER_SIZE_INT)VM_Global_State::loader_env->heap_end));
        } 
    REFS_RUNTIME_SWITCH_ENDIF
#endif // REFS_RUNTIME_OR_UNCOMPRESSED
}
#endif // _DEBUG

void 
vm_enumerate_root_reference(void **ref, BOOLEAN is_pinned)
{
    TRACE2("vm.enum", "vm_enumerate_root_reference(" 
            << ref << " -> " << *ref << ")");
#if _DEBUG
    check_ref(ref);
#endif // _DEBUG

    gc_add_root_set_entry((Managed_Object_Handle *)ref, (Boolean)is_pinned);
} //vm_enumerate_root_reference


void 
vm_enumerate_weak_root_reference(void **ref, BOOLEAN is_pinned)
{
    TRACE2("vm.enum", "vm_enumerate_weak_root_reference(" 
            << ref << " -> " << *ref << ")");
#if _DEBUG
    check_ref(ref);
#endif // _DEBUG

    gc_add_weak_root_set_entry((Managed_Object_Handle *)ref, (Boolean)is_pinned, FALSE);
} //vm_enumerate_weak_root_reference


// Resembles vm_enumerate_root_reference() but is passed the address of a U_32 slot containing a compressed reference.
VMEXPORT void vm_enumerate_compressed_root_reference(U_32 *ref, BOOLEAN is_pinned)
{
    assert(REFS_IS_COMPRESSED_MODE);

#if _DEBUG
#ifndef REFS_USE_UNCOMPRESSED
        // 20030324 Temporary: verify the slot whose reference is being passed.
        COMPRESSED_REFERENCE compressed_ref = *ref;
        ManagedObject* obj = (ManagedObject *)uncompress_compressed_reference(compressed_ref);
        bool is_null    = (compressed_ref == 0);
        bool is_in_heap = (((POINTER_SIZE_INT)VM_Global_State::loader_env->heap_base <= (POINTER_SIZE_INT)obj)
            && ((POINTER_SIZE_INT)obj <= (POINTER_SIZE_INT)VM_Global_State::loader_env->heap_end));
        assert (is_null || is_in_heap);
#endif // REFS_USE_UNCOMPRESSED
#endif // _DEBUG

    gc_add_compressed_root_set_entry(ref, (Boolean)is_pinned);
} //vm_enumerate_compressed_root_reference



// This is the main function used to enumerate interior pointers by the JITS.
// It is part of the JIT-VM interface and is currently used only by IPF Java JITs.
void 
vm_enumerate_root_interior_pointer(void **slot, size_t offset, BOOLEAN is_pinned)
{
    assert(((IDATA)offset)>=0);
    gc_add_root_set_entry_interior_pointer(slot, (int)offset, (Boolean)is_pinned);
} //vm_enumerate_root_interior_pointer

void 
vm_enumerate_root_set_global_refs()
{
    // ! The enumeration code is duplicated in !
    // ! ti_enumerate_globals(), plase apply   !
    // ! all changes there too.                !

    ////////////////////////////////////////
    ///// First enumerate strong pointers

    // Static fields of all classes
    vm_enumerate_static_fields();
    vm_enumerate_objects_to_be_finalized();
    vm_enumerate_references_to_enqueue();
    oh_enumerate_global_handles();

    ////////////////////////////////////////
    //// Now enumerate weak pointers
    vm_enumerate_interned_strings();

    extern void vm_enumerate_root_set_mon_arrays();
    vm_enumerate_root_set_mon_arrays();

    ClassLoader::gc_enumerate();

    // this enumeration part is needed only for real garbage collection,
    // and not needed for JVMTI IterateOverReachableObjects
    if (VM_Global_State::loader_env->TI->isEnabled()) {
        VM_Global_State::loader_env->TI->enumerate();
    }

} //vm_enumerate_root_set_global_refs



//
// Enumerate references associated with a thread which are not stored on
// the thread's stack.
//
VMEXPORT // temporary solution for interpreter unplug
void vm_enumerate_root_set_single_thread_not_on_stack(VM_thread *thread)
{
    assert(thread);
    if (thread->thread_exception.exc_object != NULL) {
        vm_enumerate_root_reference((void **)&(thread->thread_exception.exc_object), FALSE);
    }
    if (thread->thread_exception.exc_cause != NULL) {
        vm_enumerate_root_reference((void **)&(thread->thread_exception.exc_cause), FALSE);
    }
    if (thread->jvmti_thread.p_exception_object_ti != NULL) {
        vm_enumerate_root_reference((void **)&(thread->jvmti_thread.p_exception_object_ti), FALSE);
    }

    if (thread->native_handles)
        ((NativeObjectHandles*)(thread->native_handles))->enumerate();
    if (thread->gc_frames) {
        ((GcFrame*)(thread->gc_frames))->enumerate();
    }
} //vm_enumerate_root_set_single_thread_not_on_stack


// Enumerate references associated with a thread which are stored on the thread's stack
// (including local handles of M2nFrames) given a stack iterator for the thread's entire stack.
// Consumes the iterator.
void vm_enumerate_root_set_single_thread_on_stack(StackIterator* si)
{
    ASSERT_NO_INTERPRETER
    while (!si_is_past_end(si)) {
        CodeChunkInfo* cci = si_get_code_chunk_info(si);
        if (cci) {
#ifdef VM_STATS
            vm_stats_inc(VM_Statistics::get_vm_stats().num_unwind_java_frames_gc);
            vm_stats_inc(cci->num_unwind_java_frames_gc);
#endif
            TRACE2("enumeration", "enumerating eip=" << (void *) si_get_ip(si)
                << " is_first=" << !si_get_jit_context(si)->is_ip_past
                << " " << cci->get_method());
            cci->get_jit()->get_root_set_from_stack_frame(cci->get_method(), 0, si_get_jit_context(si));
            ClassLoader* cl = cci->get_method()->get_class()->get_class_loader();
            assert (cl);
            // force cl classloader to be enumerated as strong reference
            cl->Mark();
            if (cci->has_inline_info()) {
                JIT *jit = cci->get_jit();
                NativeCodePtr ip = si_get_ip(si);
                U_32 inlined_depth = si_get_inline_depth(si);
                if (inlined_depth) {
                    U_32 offset = (U_32)((POINTER_SIZE_INT)ip - (POINTER_SIZE_INT)cci->get_code_block_addr());
                    for (U_32 i = inlined_depth; i > 0; i--) {
                        Method* m = jit->get_inlined_method(cci->get_inline_info(), offset, i);
                        assert (m);
                        cl = m->get_class()->get_class_loader();
                        assert (cl);
                        // force cl classloader to be enumerated as strong reference
                        cl->Mark();
                    }
                }
            }
            TRACE2("enumeration", "enumerated eip=" << (void *) si_get_ip(si)
                << " is_first=" << !si_get_jit_context(si)->is_ip_past
                << " " << cci->get_method());
        } else {
#ifdef VM_STATS
            vm_stats_inc(VM_Statistics::get_vm_stats().num_unwind_native_frames_gc);
#endif
            Method* m = m2n_get_method(si_get_m2n(si));
            TRACE2("enumeration", "enumeration local handles " << m);
            oh_enumerate_handles(m2n_get_local_handles(si_get_m2n(si)));
            if (m) {
                ClassLoader* cl = m->get_class()->get_class_loader();
                assert (cl);
                // force cl classloader to be enumerated as strong reference
                cl->Mark();
            }
        }
        si_goto_previous(si, false);
    }
    si_free(si);
}
